/*******************************************************************************
* Copyright 2015 Software Evolution and Architecture Lab, University of Zurich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package eu.cloudwave.wp5.feedback.eclipse.performance.core.builders.participants;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import com.google.common.base.Optional;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.cloudwave.wp5.common.model.Procedure;
import eu.cloudwave.wp5.common.util.Numbers;
import eu.cloudwave.wp5.common.util.TimeValues;
import eu.cloudwave.wp5.feedback.eclipse.base.core.builders.participants.AbstractFeedbackBuilderParticipant;
import eu.cloudwave.wp5.feedback.eclipse.base.core.builders.participants.FeedbackBuilderParticipant;
import eu.cloudwave.wp5.feedback.eclipse.base.infrastructure.template.TemplateHandler;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.core.java.FeedbackJavaFile;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.core.java.FeedbackJavaProject;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerAttributes;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerPosition;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerSpecification;
import eu.cloudwave.wp5.feedback.eclipse.performance.Ids;
import eu.cloudwave.wp5.feedback.eclipse.performance.PerformancePluginActivator;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.ast.CollectionSourceDetector;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.ast.CollectionSourceDetector.CollectionSource;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.ast.ForLoopBodyVisitor;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.feedbackhandler.FeedbackHandlerEclipseClient;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.markers.PerformanceMarkerTypes;
import eu.cloudwave.wp5.feedback.eclipse.performance.core.properties.PerformanceFeedbackProperties;
import eu.cloudwave.wp5.feedback.eclipse.performance.infrastructure.config.PerformanceConfigs;
/**
* A builder participant that is responsible to show warnings related to collection sizes / loops.
*/
public class CriticalLoopBuilderParticipant extends AbstractFeedbackBuilderParticipant implements FeedbackBuilderParticipant {
private static final String PROCEDURE_EXECUTIONS = "procedureExecutions";
private static final String AVG_TIME_PER_ITERATION = "avgTimePerIteration";
private static final String AVG_INTERATIONS = "avgInterations";
private static final String AVG_TOTAL = "avgTotal";
private static final String LOOP = "loop";
private static final int DECIMAL_PLACES = 3;
private static final String MESSAGE_PATTERN = "Critical Loop: Average Total Time is %s (Average Iterations: %s).";
private FeedbackHandlerEclipseClient feedbackHandlerClient;
private TemplateHandler templateHandler;
public CriticalLoopBuilderParticipant() {
this.feedbackHandlerClient = PerformancePluginActivator.instance(FeedbackHandlerEclipseClient.class);
this.templateHandler = PerformancePluginActivator.instance(TemplateHandler.class);
}
/**
* {@inheritDoc}
*/
@Override
protected void buildFile(final FeedbackJavaProject project, final FeedbackJavaFile javaFile, final CompilationUnit astRoot) {
astRoot.accept(new ASTVisitor() {
private MethodDeclaration currentMethodDeclaration;
@Override
public boolean visit(final MethodDeclaration node) {
currentMethodDeclaration = node;
return true;
}
@Override
public boolean visit(final EnhancedForStatement foreachStatement) {
final Optional<CollectionSource> collectionSource = new CollectionSourceDetector().getSource(foreachStatement, currentMethodDeclaration);
if (collectionSource.isPresent()) {
final Procedure procedure = collectionSource.get().getProcedure();
final String[] arguments = procedure.getArguments().toArray(new String[procedure.getArguments().size()]);
final Double averageSize = feedbackHandlerClient.collectionSize(project, procedure.getClassName(), procedure.getName(), arguments, collectionSource.get().getPosition());
final Map<Procedure, Double> procedureExecutionTimes = getProcedureExecutionTimes(foreachStatement);
final Double avgExecTimePerIteration = getAverageExecutionTime(procedureExecutionTimes);
if (averageSize != null && avgExecTimePerIteration != null) {
final Double avgTotalExecTime = averageSize * avgExecTimePerIteration;
final double threshold = project.getFeedbackProperties().getDouble(PerformanceFeedbackProperties.TRESHOLD__LOOPS, PerformanceConfigs.DEFAULT_THRESHOLD_LOOPS);
if (avgTotalExecTime >= threshold) {
final int startPosition = foreachStatement.getParameter().getStartPosition();
final Expression expression = foreachStatement.getExpression();
final int endPosition = expression.getStartPosition() + expression.getLength();
final int line = astRoot.getLineNumber(startPosition);
final String avgIterationsText = new Double(Numbers.round(averageSize, DECIMAL_PLACES)).toString();
final String avgExecTimePerIterationText = TimeValues.toText(avgExecTimePerIteration, DECIMAL_PLACES);
final String avgTotalExecTimeText = TimeValues.toText(avgTotalExecTime, DECIMAL_PLACES);
final String msg = String.format(MESSAGE_PATTERN, avgTotalExecTimeText, avgIterationsText);
final Map<String, Object> context = Maps.newHashMap();
context.put(AVG_TOTAL, avgTotalExecTimeText);
context.put(AVG_INTERATIONS, avgIterationsText);
context.put(AVG_TIME_PER_ITERATION, avgExecTimePerIterationText);
context.put(PROCEDURE_EXECUTIONS, getProcedureExecutionData(procedureExecutionTimes));
final String desc = templateHandler.getContent(LOOP, context);
final MarkerSpecification markerSpecification = MarkerSpecification.of(Ids.PERFORMANCE_MARKER, new MarkerPosition(line, startPosition, endPosition), IMarker.SEVERITY_WARNING,
PerformanceMarkerTypes.COLLECTION_SIZE, msg).and(MarkerAttributes.DESCRIPTION, desc);
addMarker(javaFile, markerSpecification);
}
}
}
return super.visit(foreachStatement);
}
@Override
public boolean visit(final ForStatement forStatement) {
// currently only foreach-loops are supported (see method above). To also support for-loops implement this
// method as the one above. The important properties of a for loop are:
// - node.getExpression()
// - node.initializers()
// - node.updaters()
return super.visit(forStatement);
}
private Map<Procedure, Double> getProcedureExecutionTimes(final EnhancedForStatement foreachStatement) {
final List<Procedure> procedures = new ForLoopBodyVisitor().getProcedureInvocations(foreachStatement);
final Map<Procedure, Double> procedureExecutionTimes = Maps.newHashMap();
for (final Procedure procedure : procedures) {
final String[] arguments = procedure.getArguments().toArray(new String[procedure.getArguments().size()]);
final Double avgExecTimeResponse = feedbackHandlerClient.avgExecTime(project, procedure.getClassName(), procedure.getName(), arguments);
final double avgExecTime = avgExecTimeResponse != null ? avgExecTimeResponse : 0;
procedureExecutionTimes.put(procedure, avgExecTime);
}
return procedureExecutionTimes;
}
private List<ProcedureExecutionData> getProcedureExecutionData(final Map<Procedure, Double> procedureExecutionTimes) {
final List<ProcedureExecutionData> data = Lists.newArrayList();
for (final Map.Entry<Procedure, Double> entry : procedureExecutionTimes.entrySet()) {
data.add(ProcedureExecutionData.of(entry.getKey(), entry.getValue()));
}
return data;
}
private Double getAverageExecutionTime(final Map<Procedure, Double> procedures) {
double averageLoopTime = 0;
for (final Entry<Procedure, Double> entry : procedures.entrySet()) {
averageLoopTime += entry.getValue();
}
return averageLoopTime;
}
});
}
public static class ProcedureExecutionData {
private static final String POINT = ".";
private String className;
private String name;
private String executionTime;
private ProcedureExecutionData(final String className, final String name, final String executionTime) {
this.className = className;
this.name = name;
this.executionTime = executionTime;
}
public String getClassName() {
return className;
}
public String getName() {
return name;
}
public String getExecutionTime() {
return executionTime;
}
public static ProcedureExecutionData of(final Procedure procedure, final Double executionTime) {
final String simpleClassName = procedure.getClassName().substring(procedure.getClassName().lastIndexOf(POINT) + 1);
return new ProcedureExecutionData(simpleClassName, procedure.getName(), TimeValues.toText(executionTime, DECIMAL_PLACES));
}
}
}